Once an application has created a pool of threads, it can allocate them by calling the NewThread function. You specify to the NewThread function the type of thread and stack size to use, whether to use an existing thread or create a new one, the entry point function for the thread, data to pass to this function, and storage that the thread can use to return data, if any, when it terminates. The NewThread function allocates a thread from the pool (or creates a new one, depending on the options you choose) and returns the thread ID.
In Listing 1, the main function calls DoSpawnThreads to allocate threads from the thread pool. Listing 3 shows the code in DoSpawnThreads that creates a thread, the code for the thread entry point function, DoPhiloActions , and the data structure, gPhilo , for passing information to the entry point function.
#define kNumOfPhilos 5 /* Number of philopher icons*/
#define kDefaultStackSize 0 /* System determines stack size */
/* Spawn each thread from the pool of newly created threads */
void DoSpawnThreads()
{
OSErr anError;
short index;
for ( index = 0; index < kNumOfPhilos; index++ )
{
anError = NewThread(kCooperativeThread,
DoPhiloActions,
(void *)&(gPhilo[index]),
kDefaultStackSize,
kUsePremadeThread,
nil,
&(gPhilo[index].theThread));
if ( anError )
DoHandlerError("\pError in creating the New Thread
(DoSpawnThreads)", anError, kFatal);
}
}
/* Global declarations */
#define kNumberOfIterations 1000 /*Number of iterations*/
...
typedef struct { /* Resource handles where it is and whether it
has a fork */
Rect thinking_location, waiting_location, dining_location;
Rect current_location;
short left_fork, fork_state;
ThreadID theThread;
} philoRecord, *philoPtr;
philoRecord gPhilo[kNumOfPhilos]; /* global declaration */
...
/* Thread entry function */
pascal void *DoPhiloActions(void *thisPhilo)
{
short index;
for (index = 0; index < kNumberOfIterations; index++ )
{
DoThinkForAwhile();
DoGoToEat(thisPhilo);
DoPickUpLeftFork(thisPhilo);
DoPickUpRightFork(thisPhilo);
DoEatForAwhile(thisPhilo);
DoPutDownRightFork(thisPhilo);
DoPutDownLeftFork(thisPhilo);
GoToThink(thisPhilo);
}
}
As just mentioned, the NewThread function can either create a new thread or allocate an existing one from the thread pool. If you scan the parameter list for NewThread in Listing 3, you see that kUsePremade is passed as the fifth parameter. This is one of five possible options you can pass in this parameter (you sum them together if you want to use more than one) and it indicates to allocate an existing thread from the thread pool.
The first parameter to the NewThread function specifies that NewThread allocate a cooperative thread, and the fourth parameter (the stack size parameter) contains kDefaultStackSize , which specifies the default stack size. The thread pool that DoCreateTPool created in Listing 2 contains five threads and each of these uses the Thread Manager default stack size.
As you can see, the DoSpawnThreads function calls the NewThread function in a loop to allocate a number of threads. In this case, the index for the for loop is the constant kNumOfPhilos , which is set to 5. So DoSpawnThreads calls the NewThread function until it has allocated all five threads from the existing pool of threads. If there is a problem allocating the threads, DoSpawnThreads calls the error handling function and passes it the result code returned by NewThrea d.
The NewThread function uses the very last parameter to store the thread IDs of the newly created threads. At each iteration of the loop, it places the thread ID of the newly created thread in a field of the gPhilo structure. Actually, since this structure is indexed, each thread ID is stored in a separate index of the gPhilo structure.
The remaining three parameters set up the entry point to the thread. The second parameter points to DoPhiloActions as the entry point function. Since the loop in DoSpawnThreads creates five threads, DoPhiloActions is the entry point to each thread.
With the next parameter, NewThread points to a structure, gPhilo, that it passes to DoPhiloActions . This structure contains location information that is used for screen drawing and updates for each of the philosopher icons. It also contains the thread ID of each of the threads.
The NewThread function uses the second to last parameter to allocate storage for the function result from the new thread. Here it passes nil to indicate that there is no need to retrieve information from the newly created threads. See Passing Input and Output Parameters to a New Thread for information on how to set up storage to return data from a thread that you create.
By default, NewThread marks each thread that it creates as ready to run. As soon as the application executes the YieldToAnyThread function in MyEventLoop , the Thread Manager begins executing the first of the new threads and the application executes the code in DoPhiloActions .
In DoPhiloActions you can see that NewThread passes in the gPhilo structure as the thisPhilo pointer, which DoPhiloActions passes on to each of its subroutines, beginning with DoGoToEat . These subroutines use this structure to move the onscreen window icons from place to place and to "eat". For example, Listing 4 shows the code for one of the subroutines, DoEatForAwhile .
Listing 4 Using the gPhilo structure in a subroutine
void DoEatForAwhile(philoPtr thisPhilo)
{
short counter, timeToEat = Random() % kEatingTimeLimit;
thisPhilo->current_location = thisPhilo->dining_location;
for (counter = 0; counter < timeToEat; counter++)
YieldToAnyThread()
;
}
The code for DoEatForAwhile , places the icon in the dining room for a random amount of time, then yields control to another thread. The code for the other subroutines called by DoPhiloActions in Listing 3 is not shown here but it is similar: it either moves the icon into a different room, makes it stay put for awhile, or performs an action, such as lifting a fork.
When control moves to the next thread with the yield call, the same subroutines are executed as in the first thread, but they affect a different icon because the indexed data structure referenced by thisPhilo specifies five different icons in turn.
When control returns to the first thread in this sequence, it comes back to the statement in the DoEatForAwhile function after YieldToAnyThread, which was the last statement executed. Since this is the end of this subroutine, control goes back to DoPhiloActions , which then executes the next subroutine. This subroutine performs an action and then, since it also has a yield call, it yields to the next thread--the various threads continue to perform actions on the icon that they control while yielding to each thread in turn.
As you can see, the design of this application is such that the actions are controlled by one function, DoPhiloActions , and the icons are controlled by separate threads. The yield calls in each subroutine of DoPhiloActions produce the appearance of simultaneous movement of the different icons.